作者:陈广 日期:2018-2-21
我们这一系列文章主要针对的还是Web开发,所以Http协议是绕不过去的坎,这篇文章就讲述一些Http协议的基本概念,让大家对浏览器和服务器之间的交互有一个大至了解。现在有更为安全的Https,将来有机会再介绍。
为帮助大家理解本节所讲内容,会使用一个简单的例子。在Visual Studio 2017中新建.NET Core项目会有一个Web API 选项,它会创建一个最简单的Web API 应用程序,但它创建的程序实在过于简单,我的例子在它的基础上稍作更改,力求使用最简单的代码较完整展示Http的几种常用方法。
浏览器和服务器的交互是通过HTTP协议执行的,而GET和POST也是HTTP协议中的两种方法。HTTP全称为Hyper Text Transfer Protocol,中文翻译为超文本传输协议,目的是保证浏览器与服务器之间的通信。HTTP的工作方式是客户端与服务器之间的请求-应答协议。
HTTP协议中定义了浏览器和服务器进行交互的不同方法,基本方法有4种,分别是GET,POST,PUT,DELETE。这四种方法可以理解为,对服务器资源的查,增,改,删。
GET交互方式是从服务器上获取数据,而并非修改数据,所以GET交互方式是安全的。另外,对同一个URL的多个请求,得到的结果是相同的。下面写一个简单例子来演示GET请求。
在本地硬盘新建一个httpTest 文件夹,鼠标右击此文件夹,弹出菜单选择Open with Code,从而使用vscode打开此文件夹。打开终端输入如下命令创建一个空白项目:
dotnet new empty
为方便解析浏览器所发送的URL,我们需要使用.NET Core自带的路由,而路由已经集成在MVC中间件之中,所以接下来引入MVC中间件。这里涉及到中间件及路由知识,两者在.NET Core文档中都有详细介绍,我已翻译完成。有不理解的地方可以去看一眼,不看直接做例子也没问题,心里有个底再去看会更好些。
打开Startup.cs 文件,更改Startup
类代码如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
其中,services.AddMvc()
注册了MVC中间件,而app.UseMvc()
则启动中间件。
Cotroller是控制器的意思,MVC中的“C”便代表了Controller,“M”代表了数据库事务;“V”代表浏览器显示的内容;“C”则负责接收request并返回response,也就是说跟浏览器如何打交道是Controller的事。为使程序简单,我们不打算使用数据库,而直接使用一个List<string>
来存放数据。并省略了“M”,把数据直接放在Controller中。将来再一步步地复杂、分层化。
接下来在httpTest 文件夹下新建一个Controllers 文件夹,并在Controllers 文件夹下新建一个ValuesController.cs 文件。这些操作都可以很方便地在vscode的资源管理器中完成,我就不再赘述了。
在ValuesController.cs 文件中添加如下代码:
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace httpTest.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
List<string> names = new List<string>() { "张三", "李四" };
[HttpGet]
public IEnumerable<string> Get()
{
return names.ToArray();
}
}
}
在VS2017中可以添加一个控制器类,这样系统会自动生成一部分代码,减少工作量。我找了下,好象vscode没有自带这类模板,需要使用脚手架,脚手架安装起来比较麻烦,以后有机会再介绍吧。这次直接拷贝我的代码就行了。
按F5 运行程序,会自动打开浏览器,并在地址栏的地址后面添加/api/values
,按回车发现浏览器显示
["张三","李四"]
如下图所示:
本例,我们使用了一个List<string>
来代替数据库存放数据,并在里面放了两条数据“张三”和“李四”作为初始数据。
特性 [Route("api/[controller]")]
为类指定路由,它为我们访问特定的Controller指定了一个URL。其中api/
为固定字段,是访问ValuesController
所需要写的固定内容,你可以把其中的api
改成其它单词;[controller]
则表示xxxController.cs 中的xxx。由于此类的文件名为 ValuesController.cs ,所以xxx 所对应的就是Values 。那么,访问此控制器的URL就应当为api/values
。
特性 [HttpGet]
将一个方法指定为GET访问方法,也就是说当访问URL为api/values
,访问方式为GET时,将返回Get()
方法所返回的数据。
刚才我们演示的是如何返回集合中的所有数据,那么可不可以返回指定id的数据呢?我们更改程序,在ValuesController
类中添加如下代码:
[HttpGet("{id}")]
public string Get(int id)
{
return names[id];
}
运行程序,在浏览器中输入URL
http://localhost:5000/api/values/0
返回张三
在浏览器中输入URL
http://localhost:5000/api/values/1
返回李四
也就是说,这次我们在URL中的values
之后指定一个索引号,返回了此索引号所对应的单个数据。
特性[HttpGet("{id}")]
表示访问此带参的Get()
方法需要在URL中添加{id}
。由于之前我们已经分析了[HttpGet]
代表的是api/values
。所以[HttpGet("{id}")]
代表的就应该是api/values/{id}
。
http status code 是reponse的一部分,它提供了这些信息:请求是否成功,失败的原因。web api 能涉及到的status codes主要是这些: 200:OK 201:Created,创建了新的资源 204:无内容 No Content,例如删除成功 400:Bad Request,指的是客户端的请求错误. 401:未授权 Unauthorized 403:禁止操作 Forbidden。验证成功,但是没法访问相应的资源 404:Not Found 409:有冲突 Conflict 500:Internal Server Error,服务器发生了错误
这里我们在网页里见得最多的可能就是404了。之前的程序我们如果在URL中输入的是http://localhost:5000/api/values/3
,会导致服务端崩溃,这是因为集合里只有两条记录,你输入的索引超出范围自然会引发异常。此时如果我要返回404给浏览器,该如何操作呢?
首先要明确,返回string
肯定是不行的,浏览器接收字符串只会直接显示出来。.NET Core中的标准操作是返回一个IActionResult
,而如果要返回一个对象,则需包装在IActionResult
的子类ObjectResult
内。ObjectResult
内有一个StatusCode
属性,它可以存放状态码;而ObjectResult
的Value
属性则可以存放所返回的对象。当然,默认情况下,StatusCode
的值为200。下面将带参Get(int id)
方法更改如下:
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id >= 0 && id < names.Count)
{
return new ObjectResult(names[id]);
}
else
{
return NotFound();
}
}
运行程序,在打开win10自带的edge浏览器,按F12打开开发人员工具,切换到网络标签所在窗口,在地址栏输入如下地址(注意端口号可能会不一样):
http://localhost:5000/api/values/1
刷新后显示如下图所示结果:
可以看到,网络栏内的方法为GET
,结果中显示状态码为200。也就是说ObjectResult
的默认状态码为200。并非所有浏览器都自带开发人员工具,如果你机子上没有edge浏览器,可以装一个Postman来查看返回状态码,稍后我们会使用Postman。
接下来看看查找id不存在的情况。在地址栏输入如下地址:
http://localhost:5000/api/values/2
结果如下图所示:
这一次,返回了404状态码。而在我们的代码中,返回404是由NotFound()
方法完成的。NotFound()
方法内部创建一个NotFoundResult
并返回。而NotFoundResult
也是IActionResult
的子类。
实际上.NET Core还包装了Ok()
方法来返回一个带状态码200的对象,它的使用更为方便,只需将对象作为Ok()
方法参数传递进去即可。更改上述代码如下:
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id >= 0 && id < names.Count)
{
return Ok(names[id]); //此处为更改代码。
}
else
{
return NotFound();
}
}
运行后可以发现效果和new ObjectResult
是一样的。Ok()
方法返回一个OkObjectResult
,而OkObjectResult
继承自ObjectResult
。
由于Controller是每次接收请求都会创建新实例,所以我们创建的List<string>
集合是无法持久保存的。因此把此集合改为静态,以方便多次访问还能保存数据。整个ValuesController
类更改代码如下:
[Route("api/[controller]")]
public class ValuesController : Controller
{ //此集合类添加static修饰符
static List<string> names = new List<string>() { "张三", "李四" };
[HttpGet]
public IEnumerable<string> Get()
{
return names.ToArray();
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id >= 0 && id < names.Count)
{
return Ok(names[id]);
}
else
{
return NotFound();
}
}
//此处为新添加的Post代码
[HttpPost]
public void Post([FromBody] string value)
{
names.Add(value);
}
}
为方便测试,此处我们使用Postman来添加数据,ASP.NET Core文档中使用的也是这个软件。请到以下网址下载并安装: https://www.getpostman.com/
接下来运行我们刚更改好的程序,打开Postman 并进行如下设置:
http://localhost:5000/api/values
,注意端口号可能不同。"王五"
设置结果如下图所示:
接下来点击蓝色Send按钮。当然,需要再Get
一次才能看到添加结果。
接下来,把HTTP方法改为Get,再次点击Send按钮。结果如下图所示: 可以看到,字符串“王五”已经添加进集合了。
我们来看下这段新添加的代码:
[HttpPost]
public void Post([FromBody] string value)
{
names.Add(value);
}
特性[HttpPost]
表示将下面的Post
方法指定为Post访问方法,路由还是按照类上方特性[Route("api/[controller]")]
,即api/values
。
特性[FromBody]
表示请求的body里面包含着方法需要的实体数据,我们可以从它所标识的value
里获取所需数据。MVC接收json数据,所以在Postman里我们需要把上传的数据设置为json格式。在Post()
方法中,我们只是简单地将数据添加进集合内。
对于POST,成功添加后,建议的返回Status Code 是 201 (Created),还可返回新添加的数据,此功能可使用CreatedAtRoute()
实现;如果失败,可以返回400(Bad Request),可通过BadRequest()
方法实现,它和Ok()
、NotFound()
类似。接下来更改ValuesController
类代码如下:
[Route("api/[controller]")]
public class ValuesController : Controller
{
static List<string> names = new List<string>() { "张三", "李四" };
//此处给HttpGet添加一个名称,方便下面的CreatedAtRoute方法调用
[HttpGet(Name="GetAll")]
public IEnumerable<string> Get()
{
return names.ToArray();
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id >= 0 && id < names.Count)
{
return Ok(names[id]);
}
else
{
return NotFound();
}
}
[HttpPost]
public IActionResult Post([FromBody] string value)
{ //此处代码更改
if(value==null)
{
return BadRequest();
}
names.Add(value);
return CreatedAtRoute("GetAll",names.ToArray());
}
}
运行程序,按照刚才的设置操作Postman。现在每Post一次,我们都可以即时看到添加的结果。下图是Post两次之后的结果,可以看到,返回的Status Code 是 201:
CreatedAtRoute
这个内置的Helper Method可以返回一个带有地址Header的Response,这个Location Header将会包含一个URI,通过这个URI可以我们可以找到返回的数据。但是这个Action必须有一个路由的名字才可以引用它,所以在Get()
方法上的Route这个attribute里面加上Name="GetAll"
,然后在CreatedAtRoute方法第一个参数写上这个名字就可以了,尽管进行了引用,但是Post方法走完的时候并不会调用GetProduct方法,它应该只是提取里面的URI。CreatedAtRoute第二个参数是集合内的所有数据。我们可以在Postman中找到这个URI:单击Postman下方的Headers标签,查看Location项,如下图所示:
我们也可以仅仅返回新添加的数据和找到它的URI,而不是所有数据,这也是HTTP Post的标准响应方式。ValuesController
类更改代码如下:
[Route("api/[controller]")]
public class ValuesController : Controller
{
static List<string> names = new List<string>() { "张三", "李四" };
[HttpGet(Name = "GetAll")]
public IEnumerable<string> Get()
{
return names.ToArray();
}
//此处添加方法名称方便下面调用
[HttpGet("{id}", Name = "GetById")]
public IActionResult Get(int id)
{
if (id >= 0 && id < names.Count)
{
return Ok(names[id]);
}
else
{
return NotFound();
}
}
[HttpPost]
public IActionResult Post([FromBody] string value)
{
if (value == null)
{
return BadRequest();
}
names.Add(value);
//此处更改
return CreatedAtRoute("GetById", new { id = names.Count - 1 }, value);
}
}
同上添加数据“王五”,下图是返回的数据: 下图是返回的URI: 可以把URI直接放入浏览器地址栏访问,即可获取新添加的数据。
这一次CreatedAtRoute
使用的是带参Get()
方法,我们给它起了个名字GetById
,并作为CreatedAtRoute
方法的第一个参数,第二个参数对应着带参Get()
方法的参数列表,使用匿名类即可。最后一个参数返回我们刚收到的数据。
按照HTTP规范,Put请求 需要一个类似id这样的参数, 用于查找现有的数据以进行修改。更新成功后,应当返回204 (No Content)。PUT请求需要客户端发送整个更新后的实体,而不仅仅是增量。要支持部分更新,使用HTTP PATCH(本文不作介绍)。
在ValuesController
类中添加如下代码:
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] string value)
{
if (value == null || id < 0 || id >= names.Count)
{
return BadRequest();
}
names[id] = value;
return NoContent();
}
运行程序,下面我们将“张三”改为“张三丰”。打开Postman,进行如下设置:
http://localhost:5000/api/values/0
,注意端口号可能不同。"张三丰"
点击Send按钮,如下图所示:
从图可知,返回204 No Content
,说明已经更新成功。接下来在Postman中将HTTP方法设置为GET,网址变为:http://localhost:5000/api/values
,点击Send按钮查看更改,此时“张三”已经被更改为“张三丰”。如下图所示:
DELETE和PUT基本一样,它做删除操作,也需要id,操作成功后也返回204 No Content
。
在ValuesController
类中添加如下代码:
[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
if(id < 0 || id >= names.Count)
{
return BadRequest();
}
names.RemoveAt(id);
return NoContent();
}
运行程序,在Postman中设置HTTP方法为DELETE,输入网址http://localhost:5000/api/values/0
,注意端口号可能不同。运行结果如下图:
返回204,说明删除成功。Get所有数据结果如下图所示:
一篇长文,花了几天时间,总算写完,有点哆嗦,也不够完善,不过面向基础不是太好的初学者这是必要的。看完这一篇文章再去看.NET Core文档,会比较容易理解些。
;